/**
 *    Copyright (C) 2007-2009 DIVIS GmbH (http://www.divis-hagen.de/)
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation; either
 *    version 3 of the License, or (at your option) any later version.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General
 *    Public License along with this library; if not, write to the
 *    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *    Boston, MA 02110-1301 USA
 */
package de.divis_hagen.json.rs;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;


/**
 * <p>
 * This filter allows for selecting the responses content-type by specifying an extension on the
 * request-path. Currently extensions .xml and .json are supported.
 * </p>
 * <p>
 * Calling <code>/api/messages/unread.json</code> instead of <code>/api/messages/unread</code>
 * for example will wrap the <code>ServletRequest</code> and <code>ServletResponse</code> objects
 * accordingly to make the JAX-RS implementation return the appropriate content-type, JSON in this
 * case.
 * </p>
 * <p>
 * The filter-class needs to be declared and mapped in your web.xml.
 * </p>
 *
 * @author Aike J Sommer
 */
public class ContentTypeFilter implements Filter {

    private FilterConfig filterConfig = null;
//    private String p3pHeader = "CP=\"CAO PSA OUR\"";

    public ContentTypeFilter() {
    }

    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest httpReq = (HttpServletRequest) request;

        String accept;
        String originalPathInfo = httpReq.getPathInfo();
        String pathInfo;
        String callback = null;
        boolean wrapInJs = false;
        boolean discardResponse = false;

        String suffix = httpReq.getRequestURI();
        int dotPos = suffix.lastIndexOf(".");
        int slashPos = suffix.lastIndexOf("/");
        if (dotPos >= 0 && slashPos >= 0 && dotPos > slashPos && dotPos >= suffix.length() - 5) {
            suffix = suffix.substring(dotPos + 1);
        } else {
            suffix = "";
        }

        if ("xml".equals(suffix)) {
            accept = "application/xml";
            pathInfo = originalPathInfo.endsWith(".xml") ?
                originalPathInfo.substring(0, originalPathInfo.length() - 4) :
                originalPathInfo;
        } else if ("jpeg".equals(suffix)) {
            accept = "image/jpeg";
            pathInfo = originalPathInfo.endsWith(".jpeg") ?
                originalPathInfo.substring(0, originalPathInfo.length() - 5) :
                originalPathInfo;
        } else if ("json".equals(suffix)) {
            accept = "application/json";
            pathInfo = originalPathInfo.endsWith(".json") ?
                originalPathInfo.substring(0, originalPathInfo.length() - 5) :
                originalPathInfo;
        } else if ("js".equals(suffix)) {
            accept = "application/json";
            pathInfo = originalPathInfo.endsWith(".js") ?
                originalPathInfo.substring(0, originalPathInfo.length() - 3) :
                originalPathInfo;
            callback = request.getParameter("__callback");
            if (callback != null) {
                wrapInJs = true;
            } else {
                discardResponse = true;
            }
//            ((HttpServletResponse)response).setHeader("P3P", p3pHeader);
        } else {
            accept = httpReq.getHeader("Accept");
            pathInfo = originalPathInfo;
        }
//        System.out.println("Accept: " + accept);
//        System.out.println("Path-Info: " + pathInfo);

        String requestURI = httpReq.getRequestURI();
//        int index = requestURI.indexOf(originalPathInfo);
//        requestURI = requestURI.substring(0, index) + pathInfo + requestURI.substring(index +
//                originalPathInfo.length());
        if (suffix.length() > 0) {
            requestURI = requestURI.substring(0, requestURI.length() - suffix.length() - 1);
        }

        final String finalAccept = accept;
        final String finalPathInfo = pathInfo;
        final String finalRequestURI = requestURI;
        HttpServletRequestWrapper wrappedRequest = new HttpServletRequestWrapper(
                (HttpServletRequest) request) {

            @Override
            public Enumeration getHeaderNames() {
                List<String> names = Collections.list(super.getHeaderNames());
                if (!names.contains("Accept")) {
                    names.add("Accept");
                }
                return Collections.enumeration(names);
            }

            @Override
            public String getHeader(String name) {
                return "Accept".equals(name) ? finalAccept : super.getHeader(name);
            }

            @Override
            public Enumeration getHeaders(String name) {
                if ("Accept".equals(name)) {
                    List<String> result = Collections.singletonList(getHeader(name));
                    return Collections.enumeration(result);
                }
                return super.getHeaders(name);
            }

            @Override
            public String getPathInfo() {
                return finalPathInfo;
            }

            @Override
            public String getRequestURI() {
                return finalRequestURI;
            }

        };

        ByteArrayOutputStream byteOut = null;
        HttpServletResponseWrapper wrappedResponse;
        if (wrapInJs || discardResponse) {
            byteOut = new ByteArrayOutputStream();
            final ByteArrayOutputStream finalByteOut = byteOut;
            final ServletOutputStream tempOut = new ServletOutputStream() {

                @Override
                public void write(int b) throws IOException {
                    finalByteOut.write(b);
                }

            };

            wrappedResponse = new HttpServletResponseWrapper(
                    (HttpServletResponse) response) {

                private PrintWriter writer = null;

                @Override
                public ServletOutputStream getOutputStream() throws IOException {
                    return tempOut;
                }

                @Override
                public PrintWriter getWriter() throws IOException {
                    return writer == null ? (writer = new PrintWriter(getOutputStream())) : writer;
                }

            };
        } else {
            wrappedResponse = new HttpServletResponseWrapper(
                    (HttpServletResponse) response);
        }

        Throwable problem = null;

        try {
            chain.doFilter(wrappedRequest, wrappedResponse);

            if (wrapInJs || discardResponse) {
                HttpServletResponse httpResp = (HttpServletResponse) response;
                httpResp.setContentType("text/javascript;charset=UTF-8");
                httpResp.setHeader("Expires", "Sat, 6 May 1995 12:00:00 GMT");
                httpResp.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
                httpResp.addHeader("Cache-Control", "post-check=0, pre-check=0");
                httpResp.setHeader("Pragma", "no-cache");
            }

            if (wrapInJs) {
                ServletOutputStream out = response.getOutputStream();
                PrintWriter writer = new PrintWriter(out);
                writer.write(callback + "(");
                writer.flush();

                out.write(byteOut.toByteArray());
                out.flush();

                writer.write(");\n");
                writer.flush();
            }
        } catch (Throwable t) {
            // If an exception is thrown somewhere down the filter chain,
            // we still want to execute our after processing, and then
            // rethrow the problem after that.
            problem = t;
//            t.printStackTrace();
        }

        if (problem != null) {
            if (problem instanceof ServletException) {
                throw (ServletException) problem;
            }
            if (problem instanceof IOException) {
                throw (IOException) problem;
            }
            if (problem instanceof RuntimeException) {
                throw (RuntimeException) problem;
            }
            throw new RuntimeException(problem);
        }
    }

    /**
     * Destroy method for this filter 
     */
    public void destroy() {
    }

    /**
     * Init method for this filter 
     */
    public void init(FilterConfig filterConfig) {
        this.filterConfig = filterConfig;
//        if (filterConfig != null) {
//            if (filterConfig.getInitParameter("p3p-header") != null) {
//                p3pHeader = filterConfig.getInitParameter("p3p-header");
//            }
//        }
    }

    /**
     * Return a String representation of this object.
     */
    @Override
    public String toString() {
        if (filterConfig == null) {
            return ("ContentTypeFilter()");
        }
        StringBuilder sb = new StringBuilder("ContentTypeFilter(");
        sb.append(filterConfig);
        sb.append(")");
        return (sb.toString());
    }

//    private void sendProcessingError(Throwable t, ServletResponse response) {
//        String stackTrace = getStackTrace(t);
//
//        if (stackTrace != null && !stackTrace.equals("")) {
//            try {
//                response.setContentType("text/html");
//                PrintStream ps = new PrintStream(response.getOutputStream());
//                PrintWriter pw = new PrintWriter(ps);
//                pw.print("<html>\n<head>\n<title>Error</title>\n</head>\n<body>\n"); //NOI18N
//
//                // PENDING! Localize this for next official release
//                pw.print("<h1>The resource did not process correctly</h1>\n<pre>\n");
//                pw.print(stackTrace);
//                pw.print("</pre></body>\n</html>"); //NOI18N
//                pw.close();
//                ps.close();
//                response.getOutputStream().close();
//            } catch (Exception ex) {
//            }
//        } else {
//            try {
//                PrintStream ps = new PrintStream(response.getOutputStream());
//                t.printStackTrace(ps);
//                ps.close();
//                response.getOutputStream().close();
//            } catch (Exception ex) {
//            }
//        }
//    }
//
//    public static String getStackTrace(Throwable t) {
//        String stackTrace = null;
//        try {
//            StringWriter sw = new StringWriter();
//            PrintWriter pw = new PrintWriter(sw);
//            t.printStackTrace(pw);
//            pw.close();
//            sw.close();
//            stackTrace = sw.getBuffer().toString();
//        } catch (Exception ex) {
//        }
//        return stackTrace;
//    }
//
//    public void log(String msg) {
//        filterConfig.getServletContext().log(msg);
//    }

}
